﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Windows;
using System.Windows.Input;
using Xceed.Silverlight.Data;
using System.Diagnostics;

namespace Xceed.Silverlight.DataGrid
{
  #region Metadata

  [TemplatePart( Name = "GroupNavigationSearchBox", Type = typeof( GroupNavigationSearchBox ) )]

  [TemplateVisualState( GroupName = "CommonStates", Name = "Normal" )]
  [TemplateVisualState( GroupName = "CommonStates", Name = "MouseOver" )]

  [TemplateVisualState( GroupName = "CurrentStates", Name = "Current" )]
  [TemplateVisualState( GroupName = "CurrentStates", Name = "NotCurrent" )]

  [TemplateVisualState( GroupName = "ExpansionStates", Name = "Expanded" )]
  [TemplateVisualState( GroupName = "ExpansionStates", Name = "Collapsed" )]

  [TemplateVisualState( GroupName = "GroupCompletenessStates", Name = "Complete" )]
  [TemplateVisualState( GroupName = "GroupCompletenessStates", Name = "Incomplete" )]

  [TemplateVisualState( GroupName = "GroupNavigationSearchBoxVisibilityStates", Name = "GroupNavigationSearchBoxVisible" )]
  [TemplateVisualState( GroupName = "GroupNavigationSearchBoxVisibilityStates", Name = "GroupNavigationSearchBoxHidden" )]

  [TemplateVisualState( GroupName = "GroupNavigationButtonsVisibilityStates", Name = "GroupNavigationButtonsVisible" )]
  [TemplateVisualState( GroupName = "GroupNavigationButtonsVisibilityStates", Name = "GroupNavigationButtonsHidden" )]

  [TemplateVisualState( GroupName = "GroupExpansionButtonVisibilityStates", Name = "GroupExpansionButtonVisible" )]
  [TemplateVisualState( GroupName = "GroupExpansionButtonVisibilityStates", Name = "GroupExpansionButtonHidden" )]

  #endregion Metadata

  public class GroupHeaderControl : ControlBase
  {
    #region Static Fields

    internal static readonly XceedRoutedEvent OnMouseEnterGroupHeaderEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnMouseLeaveGroupHeaderEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnToggleExpansionGroupEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnExpandGroupEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnCollapseGroupEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnGroupSearchBoxFocusEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnGotoPreviousGroupEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnGotoNextGroupEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent GroupMouseLeftButtonDownEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent GroupMouseLeftButtonUpEvent = new XceedRoutedEvent();

    internal static readonly string CommonStateNormal = "Normal";
    internal static readonly string CommonStateMouseOver = "MouseOver";
    internal static readonly string CurrentStateCurrent = "Current";
    internal static readonly string CurrentStateNotCurrent = "NotCurrent";
    internal static readonly string ExpansionStateExpanded = "Expanded";
    internal static readonly string ExpansionStateCollapsed = "Collapsed";
    internal static readonly string SearchBoxVisibilityStateVisible = "GroupNavigationSearchBoxVisible";
    internal static readonly string SearchBoxVisibilityStateHidden = "GroupNavigationSearchBoxHidden";
    internal static readonly string NavigationButtonVisibilityStateVisible = "GroupNavigationButtonsVisible";
    internal static readonly string NavigationButtonVisibilityStateHidden = "GroupNavigationButtonsHidden";
    internal static readonly string ExpansionButtonVisibilityStateVisible = "GroupExpansionButtonVisible";
    internal static readonly string ExpansionButtonVisibilityStateHidden = "GroupExpansionButtonHidden";

    #endregion

    #region Constructor

    public GroupHeaderControl()
      : base()
    {
      this.DefaultStyleKey = typeof( GroupHeaderControl );
      m_visualStateHelper = new VisualStateHelper( this );

      // This is done in code behind instead of XAML because
      // this property is not supported in WPF
#if SILVERLIGHT
      TabNavigation = KeyboardNavigationMode.Cycle;
#else
      // This logic is to be reviewed for WPF.
      Debug.Assert(false );
#endif

    }

    #endregion

    #region GroupValueTemplate Property

    public static readonly DependencyProperty GroupValueTemplateProperty = DependencyProperty.Register(
      "GroupValueTemplate",
      typeof( DataTemplate ),
      typeof( GroupHeaderControl ),
      new PropertyMetadata( ( DataTemplate )null ) );

    public DataTemplate GroupValueTemplate
    {
      get
      {
        return ( DataTemplate )this.GetValue( GroupHeaderControl.GroupValueTemplateProperty );
      }
      set
      {
        this.SetValue( GroupHeaderControl.GroupValueTemplateProperty, value );
      }
    }

    #endregion

    #region CollapseGroupCommand Property (Command)

    internal static readonly DependencyProperty CollapseGroupCommandProperty = DependencyProperty.Register(
      "CollapseGroupCommand",
      typeof( ICommand ),
      typeof( GroupHeaderControl ),
      new PropertyMetadata( null ) );

    public ICommand CollapseGroupCommand
    {
      get
      {
        return ( ICommand )this.GetValue( GroupHeaderControl.CollapseGroupCommandProperty );
      }
      private set
      {
        this.SetValue( GroupHeaderControl.CollapseGroupCommandProperty, value );
      }
    }

    #endregion

    #region ExpandGroupCommand Property (Command)

    internal static readonly DependencyProperty ExpandGroupCommandProperty = DependencyProperty.Register(
      "ExpandGroupCommand",
      typeof( ICommand ),
      typeof( GroupHeaderControl ),
      new PropertyMetadata( null ) );

    public ICommand ExpandGroupCommand
    {
      get
      {
        return ( ICommand )this.GetValue( GroupHeaderControl.ExpandGroupCommandProperty );
      }
      private set
      {
        this.SetValue( GroupHeaderControl.ExpandGroupCommandProperty, value );
      }
    }

    #endregion

    #region ToggleGroupExpansionCommand Property (Command)

    internal static readonly DependencyProperty ToggleGroupExpansionCommandProperty = DependencyProperty.Register(
      "ToggleGroupExpansionCommand",
      typeof( ICommand ),
      typeof( GroupHeaderControl ),
      new PropertyMetadata( null ) );

    public ICommand ToggleGroupExpansionCommand
    {
      get
      {
        return ( ICommand )this.GetValue( GroupHeaderControl.ToggleGroupExpansionCommandProperty );
      }
      private set
      {
        this.SetValue( GroupHeaderControl.ToggleGroupExpansionCommandProperty, value );
      }
    }

    #endregion

    #region NextGroupCommand Property (Command)

    internal static readonly DependencyProperty NextGroupCommandProperty = DependencyProperty.Register(
       "NextGroupCommand",
       typeof( ICommand ),
       typeof( GroupHeaderControl ),
       new PropertyMetadata( null ) );

    public ICommand NextGroupCommand
    {
      get
      {
        return ( ICommand )this.GetValue( GroupHeaderControl.NextGroupCommandProperty );
      }
      private set
      {
        this.SetValue( GroupHeaderControl.NextGroupCommandProperty, value );
      }
    }

    #endregion

    #region PreviousGroupCommand Property (Command)

    internal static readonly DependencyProperty PreviousGroupCommandProperty = DependencyProperty.Register(
       "PreviousGroupCommand",
       typeof( ICommand ),
       typeof( GroupHeaderControl ),
       new PropertyMetadata( null ) );

    public ICommand PreviousGroupCommand
    {
      get
      {
        return ( ICommand )this.GetValue( GroupHeaderControl.PreviousGroupCommandProperty );
      }
      private set
      {
        this.SetValue( GroupHeaderControl.PreviousGroupCommandProperty, value );
      }
    }

    #endregion

    #region IsMouseOverGroupHeader Property

    internal bool IsMouseOverGroupHeader
    {
      get
      {
        return m_IsMouseOverGroupHeader;
      }

      set
      {
        m_IsMouseOverGroupHeader = value;

        if( m_IsMouseOverGroupHeader )
        {
          this.SetCommonState( GroupHeaderControl.CommonStateMouseOver, true );
          this.EventManager.RaiseEvent( GroupHeaderControl.OnMouseEnterGroupHeaderEvent, new XceedRoutedEventArgs( this ) );
        }
        else
        {
          this.SetCommonState( GroupHeaderControl.CommonStateNormal, true );
          this.EventManager.RaiseEvent( GroupHeaderControl.OnMouseLeaveGroupHeaderEvent, new XceedRoutedEventArgs( this ) );
        }
      }
    }

    private bool m_IsMouseOverGroupHeader;

    #endregion // IsMouseOverGroupHeader Property

    #region SearchBoxHasFocus Property

    internal bool SearchBoxHasFocus
    {
      get
      {
        return m_searchBoxHasFocus;
      }
      private set
      {
        m_searchBoxHasFocus = value;
        this.EventManager.RaiseEvent( GroupHeaderControl.OnGroupSearchBoxFocusEvent, new XceedRoutedEventArgs( this ) );
      }
    }

    private bool m_searchBoxHasFocus;

    #endregion SearchBoxHasFocus Property

    #region GroupPath Property

    internal DataPath GroupPath
    {
      get
      {
        return m_groupPath;
      }
      set
      {
        if( !object.Equals( value, m_groupPath ) )
        {
          m_groupPath = value;
          ( ( DataGridCommand )this.CollapseGroupCommand ).RaiseCanExecuteChanged();
          ( ( DataGridCommand )this.ExpandGroupCommand ).RaiseCanExecuteChanged();
          ( ( DataGridCommand )this.ToggleGroupExpansionCommand ).RaiseCanExecuteChanged();
        }
      }
    }

    #endregion

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();

      var oldGroupNavigationSearchBox = m_groupNavigationSearchBox;

      if( ( oldGroupNavigationSearchBox != null )
        && ( oldGroupNavigationSearchBox != m_groupNavigationSearchBox ) )
      {
        oldGroupNavigationSearchBox.GotFocus -= this.OnGroupNavigationSearchBoxGotFocus;
        oldGroupNavigationSearchBox.LostFocus -= this.OnGroupNavigationSearchBoxLostFocus;
        oldGroupNavigationSearchBox.ReleaseCommand();
      }

      m_groupNavigationSearchBox = this.GetTemplateChild( "GroupNavigationSearchBox" ) as GroupNavigationSearchBox;

      if( m_groupNavigationSearchBox != null )
      {
        m_groupNavigationSearchBox.GotFocus += this.OnGroupNavigationSearchBoxGotFocus;
        m_groupNavigationSearchBox.LostFocus += this.OnGroupNavigationSearchBoxLostFocus;
        m_groupNavigationSearchBox.AttachCommand();
      }

      m_visualStateHelper.ApplyVisualStates();
    }

    protected override void OnMouseEnter( MouseEventArgs e )
    {
      base.OnMouseEnter( e );
      this.IsMouseOverGroupHeader = true;
    }

    protected override void OnMouseMove( MouseEventArgs e )
    {
      base.OnMouseMove( e );

      Point mousePoint = e.GetPosition( this );

      if( this.IsMouseOverGroupHeader
         && ( ( mousePoint.X < 0 ) || ( mousePoint.X > this.ActualWidth )
            || ( mousePoint.Y < 0 ) || ( mousePoint.Y > this.ActualHeight ) ) )
      {
        this.IsMouseOverGroupHeader = false;
      }
      else if( !this.IsMouseOverGroupHeader
          && ( ( mousePoint.X >= 0 ) && ( mousePoint.X < this.ActualWidth ) )
          && ( ( mousePoint.Y >= 0 ) && ( mousePoint.Y < this.ActualHeight ) ) )
      {
        this.IsMouseOverGroupHeader = true;
      }
    }

    protected override void OnMouseLeave( MouseEventArgs e )
    {
      base.OnMouseLeave( e );
      this.IsMouseOverGroupHeader = false;
    }

    protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
    {
      XceedEventUtils.RaiseMouseButtonEvent( this, e, GroupHeaderControl.GroupMouseLeftButtonDownEvent );
    }

    protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e )
    {
      XceedEventUtils.RaiseMouseButtonEvent( this, e, GroupHeaderControl.GroupMouseLeftButtonUpEvent );
    }

    protected override void OnKeyDown( KeyEventArgs e )
    {
      base.OnKeyDown( e );

      switch( e.Key )
      {
        case Key.F2:
          {
            // do not bubble in parent groupContainer
            e.Handled |= m_groupNavigationSearchBox.SetFocusOnSearchTextBox();
          }
          break;

        case Key.Escape:
          {
            this.Focus();
            e.Handled = true; // do not bubble in parent groupContainer
          }
          break;

        case Key.Left:
          {
            //DataGrid will check if GroupContainer can Collapse and if groupContainer is expanded
            this.EventManager.RaiseEvent( GroupHeaderControl.OnCollapseGroupEvent, new XceedRoutedEventArgs( this ) );
            e.Handled = true; // do not bubble in parent groupContainer
          }
          break;

        case Key.Right:
          {
            //DataGrid will check if GroupContainer can expand and if groupContainer is collapsed
            this.EventManager.RaiseEvent( GroupHeaderControl.OnExpandGroupEvent, new XceedRoutedEventArgs( this ) );
            e.Handled = true; // do not bubble in parent groupContainer
          }
          break;

        default:
          break;
      }
    }

    internal void SetGroupValueTemplate( DataTemplate dataTemplate )
    {
      if( dataTemplate == null )
      {
        this.ClearValue( GroupHeaderControl.GroupValueTemplateProperty );
      }
      else
      {
        this.GroupValueTemplate = dataTemplate;
      }
    }


    internal void SetGroupNavigationSearchBoxVisibilityState( string state, bool useTransitions )
    {
      m_visualStateHelper.GoToGroupNavigationSearchBoxVisibilityState( state, useTransitions );
    }

    internal void SetGroupNavigationButtonsVisibilityState( string state, bool useTransitions )
    {
      m_visualStateHelper.GoToGroupNavigationButtonsVisibilityState( state, useTransitions );
    }

    internal void SetGroupExpansionButtonsVisibilityState( string state, bool useTransitions )
    {
      m_visualStateHelper.GoToGroupExpansionButtonsVisibilityState( state, useTransitions );
    }

    internal void SetCommonState( string state, bool useTransitions )
    {
      m_visualStateHelper.GoToCommonState( state, useTransitions );
    }

    internal void SetCurrentState( bool isCurrent, bool useTransitions )
    {
      string state = ( isCurrent ) ? GroupHeaderControl.CurrentStateCurrent : GroupHeaderControl.CurrentStateNotCurrent;
      m_visualStateHelper.GoToCurrentState( state, useTransitions );
    }

    internal void SetExpansiontState( string state, bool useTransitions )
    {
      m_visualStateHelper.GoToGroupExpansionState( state, useTransitions );
    }

    internal void CreateCommands()
    {
      this.CollapseGroupCommand = new DataGridCommand( new Action<object>( OnCollapseGroupExecute ), new Func<object, bool>( OnCollapseGroupCanExecute ) );
      this.ExpandGroupCommand = new DataGridCommand( new Action<object>( OnExpandGroupExecute ), new Func<object, bool>( OnExpandGroupCanExecute ) );
      this.ToggleGroupExpansionCommand = new DataGridCommand( new Action<object>( OnToggleGroupExpansionExecute ), new Func<object, bool>( OnToggleGroupExpansionCanExecute ) );
      this.NextGroupCommand = new DataGridCommand( new Action<object>( OnNextGroupExecute ), new Func<object, bool>( OnNextGroupCanExecute ) );
      this.PreviousGroupCommand = new DataGridCommand( new Action<object>( OnPreviousGroupExecute ), new Func<object, bool>( OnPreviousGroupCanExecute ) );
    }

    internal void ReleaseCommands()
    {
      // These commands need to be released to prevent Silverlight leak
      this.CollapseGroupCommand = null;
      this.ExpandGroupCommand = null;
      this.ToggleGroupExpansionCommand = null;
      this.NextGroupCommand = null;
      this.PreviousGroupCommand = null;
    }

    private bool OnCollapseGroupCanExecute( object parameter )
    {
      // Can Collapse if group is loaded. DataGrid will check if the group is expanded before collapsing
      return m_groupPath.LastChild.IsLoaded;
    }

    private void OnCollapseGroupExecute( object parameter )
    {
      this.EventManager.RaiseEvent( GroupHeaderControl.OnCollapseGroupEvent, new XceedRoutedEventArgs( this ) );
    }

    private bool OnExpandGroupCanExecute( object parameter )
    {
      // Can Expand if group is loaded. DataGrid will check if the group is collapsed before expanding
      return m_groupPath.LastChild.IsLoaded;
    }

    private void OnExpandGroupExecute( object parameter )
    {
      this.EventManager.RaiseEvent( GroupHeaderControl.OnExpandGroupEvent, new XceedRoutedEventArgs( this ) );
    }

    private bool OnToggleGroupExpansionCanExecute( object parameter )
    {
      // Can Toggle if group is loaded.
      return m_groupPath.LastChild.IsLoaded;
    }

    private void OnToggleGroupExpansionExecute( object parameter )
    {
      this.EventManager.RaiseEvent( GroupHeaderControl.OnToggleExpansionGroupEvent, new XceedRoutedEventArgs( this ) );
    }

    private bool OnNextGroupCanExecute( object parameter )
    {
      return true;
    }

    private void OnNextGroupExecute( object parameter )
    {
      this.EventManager.RaiseEvent( GroupHeaderControl.OnGotoNextGroupEvent, new XceedRoutedEventArgs( this ) );
    }

    private bool OnPreviousGroupCanExecute( object parameter )
    {
      return true;
    }

    private void OnPreviousGroupExecute( object parameter )
    {
      this.EventManager.RaiseEvent( GroupHeaderControl.OnGotoPreviousGroupEvent, new XceedRoutedEventArgs( this ) );
    }

    private void OnGroupNavigationSearchBoxGotFocus( object sender, EventArgs e )
    {
      this.SearchBoxHasFocus = true;
    }

    private void OnGroupNavigationSearchBoxLostFocus( object sender, EventArgs e )
    {
      this.SearchBoxHasFocus = false;
    }

    #region Private Fields

    private readonly VisualStateHelper m_visualStateHelper;
    private DataPath m_groupPath;
    private GroupNavigationSearchBox m_groupNavigationSearchBox;

    #endregion
  }
}
